import re
from urllib.parse import quote
from .route import Route

class NoReverseMatch(Exception):
    pass

class Mapper:
    def __init__(self, prefix='/'):
        if not prefix.startswith('/'):
            prefix = '/' + prefix
        if not prefix.endswith('/'):
            prefix = prefix + '/'
        self.prefix = prefix
        self.match_prefix = prefix[:-1]
        self.match_prefix_len = len(self.match_prefix)
        self.escaped_prefix = quote(prefix)
        self.named_routes = {}
        self.routes = [] 

    def connect(self, *args, **kwargs):
        route = Route(*args, **kwargs)
        self.routes.append(route)
        if route.name:
            self.named_routes[route.name] = route

    def match(self, path):
        '''
        self.match_return: None if not match, else a dict that contains controller, aciton,
                defaults, etc.
        should not raise any Exception
        
        performance: 32us (3 routes).  
        Typically routes contains id key, as makes infinite urls possibly, 
        also makes caching impossible.
        '''
        if not path.startswith(self.match_prefix): 
            return None

        match_prefix_len = self.match_prefix_len
        if len(path) > match_prefix_len and path[match_prefix_len] != '/':
            return None

        for route in self.routes:
            data = route.match(path, match_prefix_len + 1)
            if data is not None:
                return data

    def reverse(self, values, ref_values, route_name=None, check=True):
        '''
        if prefix == '/': then
            returns '/', '/?q=a', '/abc', '/abc/def?q=a', ...

        if prefix == '/app': then
            returns '/app/', '/app/?q=a', '/app/abc', '/app/abc/def?q=a', ...

        performance: from 20us to 150us(3 routes), it spends more than half time on urlencode and quote.
        performance degrades much especially when there are many query strings.
        '''
        ref_values = {k: str(v) for k, v in ref_values.items() if v is not None} if ref_values is not None else {}
        values = {k: str(v) for k, v in values.items() if v is not None} if values is not None else {}

        if not route_name:
            for route in self.routes:
                path = route.reverse(values, ref_values)
                if path is not None:
                    return self.escaped_prefix + path # path may contain query string, 
        else:    
            route = self.named_routes[route_name]
            path = route.reverse(values, ref_values, check)
            if path is not None:
                return self.escaped_prefix + path
        raise NoReverseMatch("Reverse for '%s' with values '%s' not found." % (route_name, values))
